import { Message } from '@/core/message';

/**
 * Route matching result
 */
export interface RouteMatch {
  matched: boolean;
  params?: Record<string, string>;
  score: number; // Higher score = better match
}

/**
 * Route pattern matcher
 */
export class RouteMatcher {
  private readonly pattern: string;
  private readonly regex: RegExp;
  private readonly paramNames: string[];

  constructor(pattern: string) {
    this.pattern = pattern;
    const { regex, paramNames } = this.compilePattern(pattern);
    this.regex = regex;
    this.paramNames = paramNames;
  }

  /**
   * Compile a route pattern into a regex
   */
  private compilePattern(pattern: string): { regex: RegExp; paramNames: string[] } {
    const paramNames: string[] = [];

    // Convert pattern to regex
    let regexPattern = pattern
      // Escape special regex characters except * and :
      .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
      // Handle named parameters (:param)
      .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (match, paramName) => {
        paramNames.push(paramName);
        return '([^.]+)';
      })
      // Handle wildcards (*)
      .replace(/\*/g, '([^.]*)')
      // Handle multi-level wildcards (#)
      .replace(/#/g, '(.*)');

    // Anchor the pattern
    regexPattern = `^${regexPattern}$`;

    return {
      regex: new RegExp(regexPattern),
      paramNames,
    };
  }

  /**
   * Match a routing key against the pattern
   */
  match(routingKey: string): RouteMatch {
    const match = this.regex.exec(routingKey);

    if (!match) {
      return { matched: false, score: 0 };
    }

    // Extract parameters
    const params: Record<string, string> = {};
    for (let i = 0; i < this.paramNames.length; i++) {
      params[this.paramNames[i]] = match[i + 1];
    }

    // Calculate match score (more specific patterns get higher scores)
    const score = this.calculateScore(routingKey);

    return {
      matched: true,
      params,
      score,
    };
  }

  /**
   * Calculate match score for prioritization
   */
  private calculateScore(routingKey: string): number {
    let score = 100;

    // Exact matches get highest score
    if (this.pattern === routingKey) {
      return 1000;
    }

    // Reduce score for wildcards
    const wildcardCount = (this.pattern.match(/\*/g) || []).length;
    const multiWildcardCount = (this.pattern.match(/#/g) || []).length;
    const paramCount = this.paramNames.length;

    score -= wildcardCount * 10;
    score -= multiWildcardCount * 20;
    score -= paramCount * 5;

    // Longer patterns are more specific
    score += this.pattern.length;

    return Math.max(score, 1);
  }

  /**
   * Get the original pattern
   */
  getPattern(): string {
    return this.pattern;
  }
}

/**
 * Message router for pattern-based routing
 */
export class MessageRouter {
  private readonly routes: Array<{
    matcher: RouteMatcher;
    handler: (message: Message, params?: Record<string, string>) => Promise<void> | void;
    priority: number;
  }> = [];

  /**
   * Add a route
   */
  addRoute(
    pattern: string,
    handler: (message: Message, params?: Record<string, string>) => Promise<void> | void,
    priority = 0
  ): void {
    const matcher = new RouteMatcher(pattern);
    this.routes.push({ matcher, handler, priority });

    // Sort routes by priority (higher priority first)
    this.routes.sort((a, b) => b.priority - a.priority);
  }

  /**
   * Route a message based on routing key
   */
  async route(message: Message, routingKey: string): Promise<boolean> {
    const matches: Array<{
      handler: (message: Message, params?: Record<string, string>) => Promise<void> | void;
      params?: Record<string, string>;
      score: number;
    }> = [];

    // Find all matching routes
    for (const route of this.routes) {
      const match = route.matcher.match(routingKey);
      if (match.matched) {
        matches.push({
          handler: route.handler,
          params: match.params,
          score: match.score,
        });
      }
    }

    if (matches.length === 0) {
      return false;
    }

    // Sort by score (best match first)
    matches.sort((a, b) => b.score - a.score);

    // Execute the best matching handler
    const bestMatch = matches[0];
    await bestMatch.handler(message, bestMatch.params);

    return true;
  }

  /**
   * Route a message to all matching handlers
   */
  async routeAll(message: Message, routingKey: string): Promise<number> {
    const matches: Array<{
      handler: (message: Message, params?: Record<string, string>) => Promise<void> | void;
      params?: Record<string, string>;
    }> = [];

    // Find all matching routes
    for (const route of this.routes) {
      const match = route.matcher.match(routingKey);
      if (match.matched) {
        matches.push({
          handler: route.handler,
          params: match.params,
        });
      }
    }

    // Execute all matching handlers
    await Promise.all(matches.map(match => match.handler(message, match.params)));

    return matches.length;
  }

  /**
   * Get all routes
   */
  getRoutes(): Array<{ pattern: string; priority: number }> {
    return this.routes.map(route => ({
      pattern: route.matcher.getPattern(),
      priority: route.priority,
    }));
  }

  /**
   * Clear all routes
   */
  clear(): void {
    this.routes.length = 0;
  }
}

/**
 * Header-based router
 */
export class HeaderRouter {
  private readonly rules: Array<{
    condition: (headers: Record<string, string | number | boolean>) => boolean;
    handler: (message: Message) => Promise<void> | void;
    priority: number;
  }> = [];

  /**
   * Add a header-based routing rule
   */
  addRule(
    condition: (headers: Record<string, string | number | boolean>) => boolean,
    handler: (message: Message) => Promise<void> | void,
    priority = 0
  ): void {
    this.rules.push({ condition, handler, priority });

    // Sort rules by priority
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  /**
   * Add a simple header match rule
   */
  addHeaderMatch(
    headerName: string,
    expectedValue: string | number | boolean,
    handler: (message: Message) => Promise<void> | void,
    priority = 0
  ): void {
    this.addRule(headers => headers[headerName] === expectedValue, handler, priority);
  }

  /**
   * Route message based on headers
   */
  async route(message: Message): Promise<boolean> {
    for (const rule of this.rules) {
      if (rule.condition(message.headers as Record<string, string | number | boolean>)) {
        await rule.handler(message);
        return true;
      }
    }
    return false;
  }

  /**
   * Route message to all matching handlers
   */
  async routeAll(message: Message): Promise<number> {
    const matchingHandlers: Array<(message: Message) => Promise<void> | void> = [];

    for (const rule of this.rules) {
      if (rule.condition(message.headers as Record<string, string | number | boolean>)) {
        matchingHandlers.push(rule.handler);
      }
    }

    await Promise.all(matchingHandlers.map(handler => handler(message)));
    return matchingHandlers.length;
  }

  /**
   * Clear all rules
   */
  clear(): void {
    this.rules.length = 0;
  }
}

/**
 * Content-based router
 */
export class ContentRouter {
  private readonly rules: Array<{
    condition: (content: unknown) => boolean;
    handler: (message: Message) => Promise<void> | void;
    priority: number;
  }> = [];

  /**
   * Add a content-based routing rule
   */
  addRule(
    condition: (content: unknown) => boolean,
    handler: (message: Message) => Promise<void> | void,
    priority = 0
  ): void {
    this.rules.push({ condition, handler, priority });

    // Sort rules by priority
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  /**
   * Add a property match rule
   */
  addPropertyMatch(
    propertyPath: string,
    expectedValue: unknown,
    handler: (message: Message) => Promise<void> | void,
    priority = 0
  ): void {
    this.addRule(
      content => this.getNestedProperty(content, propertyPath) === expectedValue,
      handler,
      priority
    );
  }

  /**
   * Get nested property from object
   */
  private getNestedProperty(obj: unknown, path: string): unknown {
    if (!obj || typeof obj !== 'object') {
      return undefined;
    }

    const keys = path.split('.');
    let current: any = obj;

    for (const key of keys) {
      if (current && typeof current === 'object' && key in current) {
        current = current[key];
      } else {
        return undefined;
      }
    }

    return current;
  }

  /**
   * Route message based on content
   */
  async route(message: Message): Promise<boolean> {
    for (const rule of this.rules) {
      if (rule.condition(message.body)) {
        await rule.handler(message);
        return true;
      }
    }
    return false;
  }

  /**
   * Route message to all matching handlers
   */
  async routeAll(message: Message): Promise<number> {
    const matchingHandlers: Array<(message: Message) => Promise<void> | void> = [];

    for (const rule of this.rules) {
      if (rule.condition(message.body)) {
        matchingHandlers.push(rule.handler);
      }
    }

    await Promise.all(matchingHandlers.map(handler => handler(message)));
    return matchingHandlers.length;
  }

  /**
   * Clear all rules
   */
  clear(): void {
    this.rules.length = 0;
  }
}

/**
 * Utility functions
 */

/**
 * Create a message router
 */
export function createMessageRouter(): MessageRouter {
  return new MessageRouter();
}

/**
 * Create a header router
 */
export function createHeaderRouter(): HeaderRouter {
  return new HeaderRouter();
}

/**
 * Create a content router
 */
export function createContentRouter(): ContentRouter {
  return new ContentRouter();
}

/**
 * Test if a routing key matches a pattern
 */
export function matchesPattern(pattern: string, routingKey: string): boolean {
  const matcher = new RouteMatcher(pattern);
  return matcher.match(routingKey).matched;
}

/**
 * Extract parameters from a routing key using a pattern
 */
export function extractParams(pattern: string, routingKey: string): Record<string, string> | null {
  const matcher = new RouteMatcher(pattern);
  const match = matcher.match(routingKey);
  return match.matched ? match.params || {} : null;
}
